package sequentiell;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;



/**
 * Verarbeitet eine mitgelieferte .xml, schreibt daraus generierte Werte in Variablen des OberParsers
 * @author Lukas Subel, Patrick Bender
 *
 */
public class Parser {
	//Eigene Variablen
	
	/**
	 * Menge der Kanten;
	 * speichert für jeden KnotenID die Menge an KnotenID's, auf die dieses KnotenID zeigt
	 */
	private HashMap<Integer,Set<Integer>> edgeMap = new HashMap<Integer,Set<Integer>>();
	
	String[] tokenArray;
	
	private String[] urlArray;						//wird im Konstruktor vom Thread geschrieben und nach Terminierung von diesem von der Oberklasse gelesen

	private HashMap<Integer,Node> idNodeMap;
	
	/**
	 * Konstruktor
	 * @param url individuelle Adresse mit der .xml
	 * @param graph gemeinsame HashMap; KnotenID -> Knoten
	 */
	public Parser(String[] urlArray, HashMap<Integer,Node> graph) {
		this.urlArray = urlArray;
		this.idNodeMap = graph;
	}
	
	/**
	 * Main Parser;
	 * @param url location of the .xml file
	 * @return List with all the nodes (including all the predecessors of the nodes)
	 * @throws FileNotFoundException if .xml at this location doesn't exists
	 * @throws Exception if there is a missing </graph>
	 */
	 public void goGoGo() throws Exception { 
				
		for (String url : urlArray)	 {
			//read the File and split it into Tokens
			File file = new File(url);
			readAndPrepare(file);
			//start to parse
			parse();
		}
		//start to add the edges to the nodes
		setEdges();
	}
	
	/**
	 * Parser für .xml, die kleiner sind als 10 KB; startet dann keinen eigenen Thread
	 * fuer die vorbereitenden Arbeiten
	 * @param file Eingelesenes File
	 * @throws Exception
	 */
	private void readAndPrepare(File file) throws Exception {
		FileReader fr = new FileReader(file);
		StringBuffer buffer = new StringBuffer();

		char[] arrayBuffer = new char[10000];
		int read = 0;
	
		while ( (read = fr.read(arrayBuffer)) != -1)
		{
			buffer.append(arrayBuffer,0,read);
		}

		tokenArray = buffer.toString().replaceAll("\n", "").replaceAll("\r", "").replaceAll(" ", "").split(">") ;
	}

	/**
	 * Läuft über die im Parsing Prozess abgespeicherten Kanten,
	 * die der Parser aus seiner .xml extrahiert hat, 
	 * und speichert sie in den passenden Knoten
	 */
	private void setEdges() {
		Node src = null;
		Node dst = null;
		
		//laufe über alle gespeicherten Kanten
		for (Entry<Integer, Set<Integer>> entry : edgeMap.entrySet()) {
			
			//hole den Startknoten aus der KnotenHashMap
			src = idNodeMap.get(entry.getKey());
			//iteriere über alle KnotenID's, auf die der Startknoten zeigt
			for (int dstid : entry.getValue()) {
				//hole dir den zuständigen Zielknoten
				dst = idNodeMap.get(dstid);
				//trage den Startknoten in den Predecessor der Zielknoten sein
				synchronized (dst) {
					dst.addPred(src);
				}
			}
		}
	}

	/**
	 * Parse the nodes out of the .xml.
	 * Safe the extracted edge at the variable edgeMap
	 * @throws Exception if there is an broken .xml
	 */
	private void parse() throws Exception{
	
	//Variablen fuer Knoten
	int 		capacity 		= 0;
	boolean 	capacityValid 	= false;
	boolean 	capacityInc 	= false;
	int 		id			 	= 0;
	boolean	 	idValid 		= false;
	boolean 	idInc 			= false;
	Node 		node 			= null;

	//Variablen fuer Kanten
	int 		src 			= 0;
	boolean 	srcidValid 		= false;
	boolean 	srcidInc 		= false;
	int 		dst 			= 0;
	boolean 	dstidValid 		= false;
	boolean 	dstidInc 		= false;
	boolean 	edge 			= false;
	
	//Variablen fuer Graph
	boolean 	graphBegin 		= false;
	boolean 	graphEnde 		= false;
		
	//counter for Tokens
	int i = 0;
		
	//Fallunterschiedung
		for(String line : tokenArray)
		{
				i++;
	      	  	// <graph>
				if (line.equals("<graph")) {
					if (graphBegin) throw new Exception("Nur ein <graph> pro Datei [on Token " + i + " (" + line + ">) ]");
					graphBegin = true;
				}
				// <node>
	      		else if (line.equals("<node")) {
	      			if (!graphBegin) throw new Exception("Ein <node> kann erst nach einem <graph> kommen [on Token " + i + " (" + line + ">) ]");
	      			if (node != null) throw new Exception("Ein <node> kann nicht in einem <node> stehen [on Token " + i + " (" + line + ">) ]");
	      			if (edge) throw new Exception("Ein <node> kann nicht in einem <edge> stehen [on Token " + i + " (" + line + ">) ]");
	      			node = new Node();
	      		}
				// <id>
	      		else if ( line.equals("<id") ) {
	      			if (node == null) throw new Exception("Ein <id> kann erst nach einem <node> kommen [on Token " + i + " (" + line + ">) ]");
	      			if (idInc) throw new Exception("Ein <id> kann nicht in einem <id> stehen [on Token " + i + " (" + line + ">) ]");
	      			if (capacityInc) throw new Exception("Ein <id> kann nicht in einem <capacity> stehen [on Token " + i + " (" + line + ">) ]");
	      			idInc = true;
	      		}
				// </id>
	      		else if ( line.endsWith("</id") ) {
	      			if (!(idInc)) throw new Exception("</id> ohne <id> [on Token " + i + " (" + line + ">) ]");
	      			try { id = Integer.valueOf(line.substring(0,line.indexOf('<'))) ; }
	      			catch (NumberFormatException e) { throw new Exception("" + line.substring(0,line.indexOf('<')) + " ist keine gÃ¼ltige Nummer [on Token " + i + " (" + line + ">) ]"); }
	      			idValid = true;
	      			idInc = false;
	      		}
				// <capacity>
	      		else if ( line.equals("<capacity") ) {
	      			if (node == null) throw new Exception("Ein <capacity> kann erst nach einem <node> kommen [on Token " + i + " (" + line + ">) ]");
	      			if (capacityInc) throw new Exception("Ein <capacity> kann nicht in einem <capacity> stehen [on Token " + i + " (" + line + ">) ]");
	      			if (idInc) throw new Exception("Ein <capacity> kann nicht in einem <id> stehen [on Token " + i + " (" + line + ">) ]");
	      			capacityInc = true;
	      		}
				// </capacity>
	      		else if ( line.endsWith("</capacity") ) {
	      			if (!(capacityInc)) throw new Exception("</capacity> ohne <capacity> [on Token " + i + " (" + line + ">) ]");
	      			try { capacity = Integer.valueOf(line.substring(0,line.indexOf('<'))) ; }
	      			catch (NumberFormatException e) { throw new Exception("" + line.substring(0,line.indexOf('<')) + " ist keine gÃ¼ltige Nummer [on Token " + i + " (" + line + ">) ]"); }
	      			capacityValid = true;
	      			capacityInc = false;
	      		}
				// </node>
	      		else if ( line.equals("</node") ) {
	      			if (!(node != null && capacityValid && idValid)) throw new Exception("Parameter von <node> nicht alle gesetzt oder fehlendes <node> [on Token " + i + " (" + line + ">) ]");
	      			//baue neuen Knoten
	      			node.setId(id);
	      			node.setCapacity(capacity);
	      			//füge neuen Knoten zur KnotenMap zu
	      			synchronized (idNodeMap) {
	      				//wenn Knoten schon existiert, die Kapazität aber unterschiedlich ist,
	      				//werfe Ausnahme
		      			if (idNodeMap.get(id) != null) {
		      				if (idNodeMap.get(id).getCapacity() != capacity) throw new Exception ("<node> mit gleicher id und unterschiedlicher capacity gefunden [on Token " + i + " (" + line + ">) ]");
		      			}
		      			//sonst trage Knoten in die Map ein
		      			else idNodeMap.put(id,node);
	      			}
	      			//setze Variablen zurück
	      			idValid = false;
	      			capacityValid = false;
	      			node = null;
	      		}
				//</graph>
	      		else if (line.equals("</graph")) {
	      			graphEnde = true;
				}
	
				
	      	  	// <edge>
	      		else if (line.equals("<edge")) {
					if (edge) throw new Exception("Ein <edge> kann nicht in einem <edge> stehen [on Token " + i + " (" + line + ">) ]");
					if (node != null) throw new Exception("Ein <edge> kann nicht in einem <node> stehen [on Token " + i + " (" + line + ">) ]");
					edge = true;
				}
				// <srcid>
	      		else if ( line.equals("<srcid") ) {
	      			if (!edge) throw new Exception("Ein <srcid> kann erst nach einem <edge> kommen [on Token " + i + " (" + line + ">) ]");
	      			if (srcidInc) throw new Exception("Ein <srcid> kann nicht in einem <srcId> stehen [on Token " + i + " (" + line + ">) ]");
	      			if (dstidInc) throw new Exception("Ein <srcid> kann nicht in einem <dstId> stehen [on Token " + i + " (" + line + ">) ]");
	      			srcidInc = true;
	      		}
				// </srcid>
	      		else if ( line.endsWith("</srcid") ) {
	      			if (!(srcidInc)) throw new Exception("</srcid> ohne <srcid> [on Token " + i + " (" + line + ">) ]");
	      			try { src = Integer.valueOf(line.substring(0,line.indexOf('<'))) ; }
	      			catch (NumberFormatException e) { throw new Exception("" + line.substring(0,line.indexOf('<')) + " ist keine gÃ¼ltige Nummer [on Token " + i + " (" + line + ">) ]"); }
	      			srcidInc = false;
	      			srcidValid = true;
	      		}
				// <dstid>
	      		else if ( line.equals("<dstid") ) {
	      			if (!edge) throw new Exception("Ein <dstid> kann erst nach einem <edge> kommen [on Token " + i + " (" + line + ">) ]");
	      			if (dstidInc) throw new Exception("Ein <dstid> kann nicht in einem <dstId> stehen [on Token " + i + " (" + line + ">) ]");
	      			if (srcidInc) throw new Exception("Ein <dstid> kann nicht in einem <srcId> stehen [on Token " + i + " (" + line + ">) ]");
	      			dstidInc = true;
	      		}
				// </dstid>
	      		else if ( line.endsWith("</dstid") ) {
	      			if (!(dstidInc)) throw new Exception("</dstid> ohne <dstid> [on Token " + i + " (" + line + ">) ]");
	      			try { dst = Integer.valueOf(line.substring(0,line.indexOf('<'))) ; }
	      			catch (NumberFormatException e) { throw new Exception("" + line.substring(0,line.indexOf('<')) + " ist keine gÃ¼ltige Nummer [on Token " + i + " (" + line + ">) ]"); }
	      			dstidInc = false;
	      			dstidValid = true;
	      		}
				// </edge>
	      		else if ( line.equals("</edge") ) {
	      			if (!(edge && srcidValid && dstidValid)) throw new Exception("Parameter von <edge> nicht alle gesetzt oder fehlendes <edge> (on line " + i + ")");
	      			if (edgeMap.get(src) == null) edgeMap.put(src, new HashSet<Integer>());
	      			//füge Zielknoten zur Menge vom Startknoten hinzu
	      			edgeMap.get(src).add(dst);
	      			//setze Variablen zurück
	      			srcidValid = false;
	      			dstidValid = false;
	      			edge = false;
	      		}
				// tokens der node phase
	  	    	else if (line.equals("</graph") || line.equals("<graph") || line.equals("<node") || line.equals("</node") || line.equals("<id") || line.endsWith("</id") || line.equals("<capacity") || line.endsWith("</capacity") || line.equals("") ) continue;
	  	    	// sonstiges
	  	    	else throw new Exception("Unerwartete Zeichenfolge! [on Token " + i + " (" + line + ">) ]");
				
				
		
	      	}
		
		//check if we finally reached the graph end and everything is good
		if (!graphEnde) throw new Exception("Fehlendes </graph> am Ende der Datei");
		if (idInc) throw new Exception("Fehlendes </id> am Ende der Datei");
		if (capacityInc) throw new Exception("Fehlendes </capacity> am Ende der Datei");
		if (srcidInc) throw new Exception("Fehlendes </srcid> am Ende der Datei");
		if (dstidInc) throw new Exception("Fehlendes </dstid> am Ende der Datei");
		if (node != null) throw new Exception("Fehlendes </node> am Ende der Datei");
		if (edge) throw new Exception("Fehlendes </edge> am Ende der Datei");
		
		}
	
}